<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>JOR1K Compiler Demo</title>
        <link rel="stylesheet" href="css/default.css">
    </head>
    <body onload="Start()">
    <div class="heading">
    <h1>Web-based C-Compiler demo for <a href='.'>jor1k</a></h1>
    </div>
    
    <div >
        <table>
            <tr>
                <td style="vertical-align: top">
                    <table>
                    <tr><td>
                    <table id="tty" class="terminal" cellpadding="0" cellspacing="0">
                    </table>
                    </td></tr>
                    <tr><td>
                    <span style="float: left">
                    <form>VM Control
                    <button type='button' onclick='term_output="";waitingForActivate=true;jor1kgui.Reset()'>Reset</button>
                    <button type='button' onclick='jor1kgui.Pause(true)'>Pause</button>
                    <button type='button' onclick='jor1kgui.Pause(false)'>Run</button>
                    </form>
                    <input type="text" id="loadPath" style="height: 20px;" value="/home/user" />
                    <label>
                        <img src="images/upload.png" width="20px" height="20px" title="Upload to folder" />
                        <input type="file" id="files" style="visibility:hidden;width:1px; height:1px;" name="files[]" onchange='OnUploadFiles(this.files)' multiple />
                    </label>
                    <img src="images/tar.png" width="20px" height="20px"  onclick='jor1kgui.fs.TAR(jor1k.fs.TAR(document.getElementById("loadPath").value))' title="Download of folder" />
                    <img src="images/sync.png" width="20px" height="20px"  onclick='jor1kgui.fs.Sync("home/user")' title="Sync with server" />
                    <textarea style="display:none;" id="clipboard">clipb</textarea>
                    </span>
                    <span id="stats" style="float: right"></span>
                    </td></tr>
                    </table>

                </td>
                <td style="vertical-align: top">
<div id="code" style="width: 640px; height: 388px; margin-top: 2px">
// jor1k Example: Client-side C Compiler demo 
// by Neelabh Gupta and Lawrence Angrave from UIUC

// Compile this program to find the syntax error...
#include &lt;stdio.h&gt;
int main() {
  
    printf("Hello World!\n");
    return 0;
}
</div>

<select id="lang" onchange='editor.setValue("");editor.getSession().setMode("ace/mode/"+this.options[this.selectedIndex].value)'>
  <option value="c_cpp">gcc</option>
  <option value="lua">lua</option>
  <option value="perl">perl</option>
  <option value="php">php</option>
  <option value="python">python</option>
  <option value="ruby">ruby</option>
  <option value="sh">sh</option>
</select> options:<input type="text" maxlen=32 id="gccoptions" value="-Wall" /><button type='button' onclick= "RunCode(GetCodeText(),document.getElementById('gccoptions').value);return false;" >Compile and run</button>
<span style="margin: 0 5px 0 5px" id="gcc-compile-status"></span>
<span style="margin: 0 5px 0 5px">Errors: <span id="gcc-error-count">0</span> Warnings: <span id="gcc-warning-count">0</span></span>
Theme<select onchange='editor.setTheme("ace/theme/"+this.options[this.selectedIndex].value)'>
  <option value="monokai">monokai</option>
  <option value="terminal">terminal</option>
  <option value="tomorrow">tomorrow</option>
  <option value="xcode">xcode</option>
</select>

                </td>
            </tr>
        </table>
       
        </div>
        <div class="infobox">
        <h3>About</h3>
       <ul>This interactive compile demo is by Neelabh Gupta and Lawrence Angrave from UIUC. It uses the ACE editor and jor1k's' gcc to compile the student's C program.</ul>
       <ul>The output of gcc is used to highlight errors in the student's program. </ul>
       <br>
       <ul>This compiler demo is dual licensed under the GPL v3 and Illinois/NCSA Open Source license.</ul>
       <ul>The ACE editor is licensed under the BSD license.</ul>
       <br>
    
       <ul>Lawrence and UIUC students are building a more complete learning environment to playfully learn C and system programming at <a href='http://cs-education.github.io/sys'>cs-education.github.io/sys</a>.</ul>
       <br>
        </div>

<script src="ace-editor-src-min-noconflict/ace.js"></script>
<script src="jor1k-master-min.js"></script>

<script>

var Jor1k = require("Jor1k");
var LinuxTerm = require("LinuxTerm");


function OnUploadFiles(files)
{
    var path = document.getElementById("loadPath").value;
    for (var i = 0, f; f = files[i]; i++) {
        jor1kgui.fs.UploadExternalFile(path, f);
    }
}

function RandomString(length) {
    var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    var result = '';
    for (var i = length; i > 0; --i) result += chars[Math.round(Math.random() * (chars.length - 1))];
    return result;
}

function Start() {
    CreateEditor();

    var userid = RandomString(10);

    var term = new LinuxTerm("tty");   // canvas id for the terminal

    jor1kparameters = {
        path: "../openrisc-sys/",

        system: {
            kernelURL: "kernel/vmlinux.bin.bz2", // kernel image
            memorysize: 32, // in MB, must be a power of two
            cpu: "asm", // short name for the cpu to use
            ncores: 1,
        },
        fs: {
            basefsURL: "basefs-compile.json", // json file with the basic filesystem configuration.
            extendedfsURL: "fs.json", // json file with extended filesystem informations. Loaded after the basic filesystem has been loaded.
            earlyload: [
                "usr/bin/gcc", 
                "usr/libexec/gcc/or1k-linux-musl/4.9.0/cc1",
                "usr/libexec/gcc/or1k-linux-musl/4.9.0/collect2",
                "usr/lib/libbfd-2.24.51.20140817.so",
                "usr/lib/gcc/or1k-linux-musl/4.9.0/libgcc.a",
                "usr/bin/as",
                "usr/include/stdio.h"
            ], // list of files which should be loaded immediately after they appear in the filesystem
            lazyloadimages: [
            ] // list of automatically loaded images after the basic filesystem has been loaded
        },
        term: term,
        clipboardid: "clipboard",  // input id for the clipboard
        statsid: "stats",  // object id for the statistics test
        relayURL: "", // relay url for the network
        userid: userid, // unique user id string. Empty, choosen randomly, from a url, or from a cookie.
        syncURL: "//jor1k.com/sync/upload.php", // url to sync a certain folder
        fps: 10, // update interval of framebuffer
        memorysize: 32, // in MB, must be a power of two
    }

    // --------------------------------------------------------
    // parse URL
    var loc = document.URL.split('?');
    userid = "";
    if (loc[1]) {
        var params = loc[1].split('&');
        for(var i=0; i<params.length; i++) {
            if (params[i].substr(0,4) == "user") {
                userid = params[i].split('=')[1];
                jor1kparameters.userid = userid;
                jor1kparameters.fs.lazyloadimages.push("//jor1k.com/sync/tarballs/"+userid+".tar.bz2");
            }
            if (params[i].substr(0,3) == "cpu") {
                jor1kparameters.system.cpu = params[i].split('=')[1];
                if (jor1kparameters.system.cpu == "smp") {
                    console.log("Load smp kernel");
                    jor1kparameters.system.kernelURL = "kernel/vmlinuxsmp.bin.bz2";
                    jor1kparameters.system.ncores = 4;
                }
            }
            if (params[i].substr(0,1) == "n") {
                jor1kparameters.system.ncores = params[i].split('=')[1];
            }
        }
    }

    if (userid == "") {
        if (loc[1])
              window.history.pushState([], "", location + "&user="+jor1kparameters.userid);
          else
              window.history.pushState([], "", location + "?user="+jor1kparameters.userid);
    }

    // --------------------------------------------------------

    jor1kgui = new Jor1k(jor1kparameters);
    term.SetCharReceiveListener(terminalPutCharListener);
}

var term_output = "";
var gcc_output;
var gcc_output_capture_re = /###GCC_COMPILE###\s*([\S\s]*?)\s*###GCC_COMPILE_FINISHED###/;
var gcc_exit_code_capture_re = /GCC_EXIT_CODE: (\d+)/;

var waitingForActivate = true;
function terminalPutCharListener(c) {
    term_output = term_output + c;
    if(waitingForActivate  && (term_output.indexOf('activate this console.') != -1)) {
      SendKeysToJor1k("\n");
      waitingForActivate = false;
      // waitingForActivate hack can be removed once busybox inittab is updated to use respawn.
    }
}

function waitForGccCompletion() {
    var regex_match_array = gcc_output_capture_re.exec(term_output);
    var gcc_exit_code, errorAnnotations;
    var warnings_errors_info_count = {
        error: 0,
        warning: 0,
        info: 0
    };

    if (regex_match_array !== null) {
        gcc_output = regex_match_array[1];
        gcc_exit_code = parseInt(gcc_exit_code_capture_re.exec(gcc_output)[1]);
        SendKeysToJor1k("clear\n");
        errorAnnotations = getErrorAnnotations(gcc_output);
        editor.getSession().setAnnotations(errorAnnotations);
        errorAnnotations.forEach(function (errorAnnotation) {
            warnings_errors_info_count[errorAnnotation.type] += 1;
        });

        document.getElementById('gcc-error-count').innerHTML = warnings_errors_info_count.error.toString();
        document.getElementById('gcc-warning-count').innerHTML = warnings_errors_info_count.warning.toString();

        if (gcc_exit_code === 0) {
            if (warnings_errors_info_count.warning > 0) {
                document.getElementById('gcc-compile-status').innerHTML = '<span><a href="#" style="color: darkgoldenrod" onclick="alert(gcc_output); return false;">SUCCESSFUL WITH WARNINGS</a></span>';
            }
            else {
                document.getElementById('gcc-compile-status').innerHTML = '<span style="color: green">SUCCESS</span>';
            }
            SendKeysToJor1k("./program\n");
        }
        else {
            document.getElementById('gcc-compile-status').innerHTML = '<span><a href="#" style="color: red" onclick="alert(gcc_output); return false;">FAILED</a></span>';
        }
    }
    else {
        // output is not ready yet
        document.getElementById('gcc-compile-status').innerHTML = '<span style="color: gray">COMPILING</span>';
        setTimeout(waitForGccCompletion, 1000);
    }
}

var gcc_output_parse_re = /(?:prog\.c|gcc|collect2):\s*(.+)\s*:\s*(.+)\s*/;
var gcc_row_col_type_parse_re = /(\d+):(\d+):\s*(.+)/;
var gcc_output_type_text_split_re = /\s*(.+)\s*:\s*(.+)\s*/;
function getErrorAnnotations(gcc_output_str) {
    var match, line_col_type_match, type_text_split_match, row, col, gcc_error_type, ace_annotation_type, text, errors = [];
    gcc_output_str.split("\n").forEach(function (error_line) {
        if(match = gcc_output_parse_re.exec(error_line)) {
            if (line_col_type_match = gcc_row_col_type_parse_re.exec(match[1])) {
                row = line_col_type_match[1] - 1; // line numbers in ace start from zero
                col = line_col_type_match[2];
                if (type_text_split_match = gcc_output_type_text_split_re.exec(line_col_type_match[3])) {
                    gcc_error_type = type_text_split_match[1];
                    text = type_text_split_match[2] + ": " + match[2];
                } else {
                    gcc_error_type = line_col_type_match[3];
                    text = match[2];
                }
            } else {
                // some gcc output without line info
                row = col = 0;
                if (type_text_split_match = gcc_output_type_text_split_re.exec(match[1])) {
                    gcc_error_type = type_text_split_match[1];
                    text = type_text_split_match[2] + ": " + match[2];
                } else {
                    gcc_error_type = match[1];
                    text = match[2];
                }
            }

            // Determine the type of editor annotation. ace supports error, warning or info.
            // This annotation type is also used to determine success of the compilation process.
            if (gcc_error_type.toLowerCase().indexOf("error") !== -1) {
                ace_annotation_type = "error";
            } else if (gcc_error_type.toLowerCase().indexOf("warning") !== -1) {
                ace_annotation_type = "warning";
            } else {
                ace_annotation_type = "info";
            }

            errors.push({
                row: row,
                column: col,
                type: ace_annotation_type,
                text: text
            });
        }
    });
    return errors;
}

function CreateEditor()
{
    editor = ace.edit("code");
    editor.setTheme("ace/theme/monokai");
    editor.getSession().setNewLineMode("unix");
    editor.getSession().setMode("ace/mode/c_cpp");
}

function GetCodeText() {
   //return document.getElementById('code').value; // textarea
   return editor.getSession().getValue(); //ace
}

function SendKeysToJor1k(text,quiet) {
    var tty = quiet ? "tty1" : "tty0";
    for(var i=0;i<text.length;i++) {
        jor1kgui.message.Send(tty, [text.charCodeAt(i) >>>0]);
    }
}

function RunCode(code,gccoptions) {
    if(code.length ===0 || code.indexOf("\x03") >= 0 || code.indexOf("\x04") >= 0 ) return;           

    jor1kgui.Pause(false);
    // wakeup tty0
    SendKeysToJor1k("\x03\n\nstty -clocal\nstty crtscts\nstty -ixoff\n",false); 
    // wakeup hidden tty1
    //tty1-unusedfornow SendKeysToJor1k("\x03\n",true);
   
    //SendKeysToJor1k("cd ~;rm prog.c program 2>/dev/null\n");
    var bufView = new Uint8Array(code.length);
    for (var i=0; i<code.length; i++) {
        bufView[i] = code.charCodeAt(i);
    }
    jor1kgui.message.Send("MergeFile", {name: "home/user/prog.c", data: bufView});
    
    var lang = document.getElementById("lang").value;
    if(lang == "c_cpp"){  
        // For now we want gcc output to be viewable, so this goes on the main terminal
        // To use tty1 we would need to synchronize gcc i.e. wait for prog.c upload

        // not-reliable SendKeysToJor1k("stty echo\n\nclear\n");
        term_output = "";
        SendKeysToJor1k("echo \\#\\#\\#GCC_COMPILE\\#\\#\\#;clear;gcc " + gccoptions + " prog.c -o program; echo GCC_EXIT_CODE: $?; echo \\#\\#\\#GCC_COMPILE_FINISHED\\#\\#\\#;clear\n");
        waitForGccCompletion();
    } else {
        document.getElementById('gcc-compile-status').innerHTML = "";
        SendKeysToJor1k("mv prog.c prog\nclear\n" + lang + " prog\n");
    }
}
</script>

    </body>
</html>
